﻿using System;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Shell;
using Microsoft.Phone.Controls;
using WindowsPhoneMvp.Navigation;
using WindowsPhoneMvp.Util;

namespace WindowsPhoneMvp.Events
{
    public sealed class ItemToPresenter
    {
        public static readonly DependencyProperty EventProperty = DependencyProperty.RegisterAttached("Event", typeof(AttachedEventMessage), typeof(ItemToPresenter), new PropertyMetadata(null));
        static readonly MethodInfo routedEventHandlerDefinition = typeof(ItemToPresenter).GetMethod("RoutedEventHandler", BindingFlags.Public | BindingFlags.Static).GetGenericMethodDefinition();

        public static AttachedEventMessage GetEvent(FrameworkElement element)
        {
            return element.GetValue(EventProperty) as AttachedEventMessage;
        }

        public static void SetEvent(FrameworkElement element, AttachedEventMessage value)
        {
            element.SetValue(EventProperty, value);
            Attach(() => element, element, value);
        }

        public static void SetMenuItem(IApplicationBarMenuItem element, AttachedEventMessage value)
        {
            var e = Application.Current.RootVisual as PhoneApplicationFrame;
            if(e != null && e.Content is PhoneApplicationPage)
                Attach(() => e.Content as PhoneApplicationPage, element, value);
            else
            { //root element doesn't exist yet, try to attach at a later time
                NavigatedEventHandler delayedSetMenuItemAttach = null;
                delayedSetMenuItemAttach = (s, args) =>
                {
                    NavigationServiceContext.RootApplicationContext.Navigated -= delayedSetMenuItemAttach;
                    var innerFrame = Application.Current.RootVisual as PhoneApplicationFrame;
                    if(innerFrame != null && innerFrame.Content is PhoneApplicationPage)
                        Attach(() => innerFrame.Content as PhoneApplicationPage, element, value);
                };
                NavigationServiceContext.RootApplicationContext.Navigated += delayedSetMenuItemAttach;
            }
        }

        public static AttachedEventMessage GetMenuItem(IApplicationBarMenuItem element)
        {
            return null;
        }

        public static void SetIconButton(IApplicationBarIconButton element, AttachedEventMessage value)
        {
            var e = Application.Current.RootVisual as PhoneApplicationFrame;
            if(e != null && e.Content is PhoneApplicationPage)
                Attach(() => e.Content as PhoneApplicationPage, element, value);
            else
            { //root element doesn't exist yet, try to attach at a later time
                NavigatedEventHandler delayedSetIconButtonAttach = null;
                delayedSetIconButtonAttach = (s, args) =>
                    {
                        NavigationServiceContext.RootApplicationContext.Navigated -= delayedSetIconButtonAttach;
                        var innerFrame = Application.Current.RootVisual as PhoneApplicationFrame;
                        if(innerFrame != null && innerFrame.Content is PhoneApplicationPage)
                            Attach(() => innerFrame.Content as PhoneApplicationPage, element, value);
                    };
                NavigationServiceContext.RootApplicationContext.Navigated += delayedSetIconButtonAttach;
            }

        }
        public static AttachedEventMessage GetIconButton(IApplicationBarIconButton element)
        {
            return null;
        }

        public static void Attach(Func<FrameworkElement> elementResolve, object source, AttachedEventMessage message)
        {
            var element = elementResolve();

            foreach (var ev in message.EventData.EventItems)
            {
                if (string.IsNullOrEmpty(ev.TargetMethod) && string.IsNullOrEmpty(element.Name))
                    throw new ArgumentNullException("elementResolve", "The element hosting the Event must be named.");

                if (string.IsNullOrEmpty(ev.TargetMethod))
                    ev.TargetMethod = element.Name;

                var eventInfo = source.GetType().GetEvent(ev.SourceEvent);

                if (eventInfo == null)
                    throw new ArgumentException(string.Format("Event '{0}' was not found on '{1}'",
                                                              ev.SourceEvent,
                                                              element.Name ?? element.GetType().Name));

                var handlerMethod = eventInfo.EventHandlerType.GetMethod("Invoke");
                var eventArgs = handlerMethod.GetParameters()[1];
                var routedEventHandler = routedEventHandlerDefinition.MakeGenericMethod(eventArgs.ParameterType);
                routedEventHandler.Invoke(null, new[] {elementResolve, source, eventInfo, ev});
            }
        }

        public static void RoutedEventHandler<TArgs>(Func<FrameworkElement> visualSourceResolve, object eventSource, EventInfo eventInfo, EventDataItem data)
        {
            Action<object, TArgs> handlerClosure = (x, y) =>
                    {
                        var visualSource = visualSourceResolve();
                        var parentView = visualSource.AncestorsAndSelf<IView>().FirstOrDefault() as IView;
                        if(parentView == null)
                        { // falls back to the current page
                            var e = Application.Current.RootVisual as PhoneApplicationFrame;
                            if(e != null && e.Content is PhoneApplicationPage)
                                parentView = e.Content as IView;
                        }
                        if(parentView != null && parentView is IEventRoutingView)
                        {
                            ((IEventRoutingView)parentView).EventToPresenter.Invoke(visualSource, eventSource, data, y);
                        }
                        else
                        {
                            throw new ActionNotSupportedException(string.Format(
                                @"Couldn't find an IView as an Ancestor of {0}", visualSource.Name ?? visualSource.GetType().Name));
                        }
                    };
            var del = Delegate.CreateDelegate(eventInfo.EventHandlerType, handlerClosure.Target, handlerClosure.Method);
            eventInfo.AddEventHandler(eventSource, del);
        }
    }
}
